package com.thinkingcoder.tcrpc.core.provider;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.util.MultiValueMap;

import com.thinkingcoder.tcrpc.core.api.RpcContext;
import com.thinkingcoder.tcrpc.core.api.RpcException;
import com.thinkingcoder.tcrpc.core.api.RpcRequest;
import com.thinkingcoder.tcrpc.core.api.RpcResponse;
import com.thinkingcoder.tcrpc.core.config.ProviderProperties;
import com.thinkingcoder.tcrpc.core.governance.SlidingTimeWindow;
import com.thinkingcoder.tcrpc.core.meta.ProviderMeta;
import com.thinkingcoder.tcrpc.core.util.TypeUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * invoke the service methods in provider.
 */
@Slf4j
public class ProviderInvoker {

    private MultiValueMap<String, ProviderMeta> skeleton;

    final ProviderProperties providerProperties;

    final Map<String, SlidingTimeWindow> windows = new HashMap<>();

    public ProviderInvoker(ProviderBootstrap providerBootstrap) {
        this.skeleton = providerBootstrap.getSkeleton();
        this.providerProperties = providerBootstrap.getProviderProperties();
    }

    /**
     * 执行服务消费者的远程调用
     *
     * @param request
     * @return
     */
    public RpcResponse<Object> invoke(RpcRequest request) {

        log.debug(" ===> ProviderInvoker.invoker(request : {} )", request);

        if (!request.getParams().isEmpty()) {
            request.getParams().forEach(RpcContext::setContextParameter);
        }

        String serviceName = request.getService();
        String methodSign = request.getMethodSign();
        Object[] args = request.getArgs();

        int trafficControl = Integer.parseInt(providerProperties.getMetas().getOrDefault("tc", "20"));
        log.debug(" ===> trafficControl : {} for {}", trafficControl, serviceName);

        RpcResponse<Object> rpcResponse = new RpcResponse<>();

        synchronized (windows) {
            SlidingTimeWindow window = windows.computeIfAbsent(serviceName, k -> new SlidingTimeWindow());
            if (window.calcSum() >= trafficControl) {
                System.out.println(window);
                throw new RpcException("service " + serviceName + " invoked in 30s/[" + window.getSum()
                    + "] larger than tpsLimit = " + trafficControl, RpcException.ExceedLimitEx);
            }
            window.record(System.currentTimeMillis());
            log.debug("service {} in window with {}", serviceName, window.getSum());
        }

        // 通过服务的接口全限定名，获取具体的服务类
        List<ProviderMeta> providerMetas = skeleton.get(serviceName);
        try {
            ProviderMeta meta = findProviderMeta(providerMetas, methodSign);
            // 使用反射的方式，调用具体指定的方法，并获取执行结果
            Method method = meta.getMethod();
            // 将入参与时间参数，进行匹配转换，防止出现类型丢失
            Object[] convertedArgs = processArgs(args, method.getParameterTypes(), method.getGenericParameterTypes());
            Object result = method.invoke(meta.getServiceImpl(), convertedArgs);

            rpcResponse.setStatus(true);
            rpcResponse.setData(result);

        } catch (InvocationTargetException e) {
            rpcResponse.setEx(new RpcException(e.getTargetException().getMessage()));
        } catch (IllegalAccessException | IllegalArgumentException e) {
            rpcResponse.setEx(new RpcException(e.getMessage()));
        } finally {
            // 防止内存泄露和上下文污染
            RpcContext.ContextParameters.get().clear();
        }
        log.debug(" ===> ProviderInvoker.invoke() = {} ", rpcResponse);
        return rpcResponse;
    }

    private Object[] processArgs(Object[] args, Class<?>[] parameterTypes, Type[] genericTypes) {
        if (args == null || args.length == 0) {
            return args;
        }
        Object[] convertedArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            convertedArgs[i] = TypeUtils.cast(args[i], parameterTypes[i], genericTypes[i]);
        }
        return convertedArgs;
    }

    private ProviderMeta findProviderMeta(List<ProviderMeta> providerMetas, String methodSign) {
        Optional<ProviderMeta> optional =
            providerMetas.stream().filter(temp -> temp.getMethodSign().equalsIgnoreCase(methodSign)).findFirst();
        return optional.orElse(null);
    }
}
